//=============================================================================
//     _   _                               ____                _  _          
//    | | | |  __ _  _ __   _ __   _   _  / ___|  _ __   _ __ (_)| |_   ___ 
//    | |_| | / _` || '_ \ | '_ \ | | | | \___ \ | '_ \ | '__|| || __| / _ \
//    |  _  || (_| || |_) || |_) || |_| |  ___) || |_) || |   | || |_ |  __/
//    |_| |_| \__,_|| .__/ | .__/  \__, | |____/ | .__/ |_|   |_| \__| \___|
//                  |_|    |_|     |___/         |_|                         
//
//                     HappySprite - We make sprites happy
//
// Copyright (c) 2007 by Tank Monkey Games
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//=============================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using HappySprite.Graphics;

namespace HappySprite.SceneGraph
{
    public class SceneItem
    {
        #region Scene graph building and traversal

        SceneItem parent;
        List<SceneItem> childern;

        static readonly IEnumerable<SceneItem> emptyChildern = new SceneItem[0];

        /// <summary>
        /// Gets all the childern for this <see cref="SceneItem"/>.
        /// </summary>
        /// <value>An enumeration of all the childern for this <see cref="SceneItem"/>.</value>
        public IEnumerable<SceneItem> Childern
        {
            get
            {
                return childern ?? emptyChildern;
            }
        }

        /// <summary>
        /// Gets this <see cref="SceneItem"/> and all of its descendants.
        /// </summary>
        /// <value>An enumeration of this <see cref="SceneItem"/> and all of its descendants.</value>
        public IEnumerable<SceneItem> DepthFirstTraversal
        {
            get
            {
                // The stack of nodes left to traverse
                Stack<SceneItem> stack = new Stack<SceneItem>();

                // Put self in stack
                stack.Push(this);

                while (stack.Count > 0)
                {
                    // Get next node
                    SceneItem current = stack.Pop();

                    // If current node has childern, push on stack
                    if (current.childern != null)
                        for (int i = current.childern.Count - 1; i >= 0; --i)
                            stack.Push(current.childern[i]);

                    // Yield the current node
                    yield return current;
                }
            }
        }

        /// <summary>
        /// Gets or sets the parent <see cref="SceneItem"/>.
        /// </summary>
        /// <value>The parent <see cref="SceneItem"/>.</value>
        public SceneItem Parent
        {
            get
            {
                return parent;
            }
            set
            {
                if (parent == value)
                    return;

                if (value != null)
                    value.Add(this);
                else
                    parent.Remove(this);
            }
        }

        /// <summary>
        /// Gets the root <see cref="SceneItem"/>.
        /// </summary>
        /// <value>The root <see cref="SceneItem"/>.</value>
        public SceneItem Root
        {
            get
            {
                SceneItem root = this;

                while (root.parent != null)
                    root = root.parent;

                return root;
            }
        }

        /// <summary>
        /// Adds the specified <see cref="SceneItem"/>.
        /// </summary>
        /// <param name="item">The <see cref="SceneItem"/> to add.</param>
        /// <exception cref="ArgumentNullException"><see cref="SceneItem"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentExcpetion">adding <see cref="SceneItem"/> would cause a circular reference.</exception>
        public void Add(SceneItem item)
        {
            // Argument check
            if (item == null) throw new ArgumentNullException("item");

            // If item's parent is us, then the 
            // item is already in our childern collection
            if (item.parent == this)
            {
                Debug.Assert(childern != null);
                Debug.Assert(childern.Contains(item));
                return;
            }

            CheckParentingCycle(this, item);

            item.AssignParent(this);

            // Since most SceneItems are not expected to have childern,
            // the childern collection is lazy created for performance and
            // to minimize wasted memory.
            if (childern == null)
                childern = new List<SceneItem>();

            childern.Add(item);
        }

        /// <summary>
        /// Clears all childern for this <see cref="SceneItem"/>.
        /// </summary>
        public void Clear()
        {
            if (childern != null)
                while (childern.Count > 0)
                    childern[0].Parent = null;
        }

        /// <summary>
        /// Removes the specified <see cref="SceneItem"/>.
        /// </summary>
        /// <param name="item">The <see cref="SceneItem"/> to remove.</param>
        /// <exception cref="ArgumentNullException">item is <c>null</c>.</exception>
        public void Remove(SceneItem item)
        {
            // Argument check
            if (item == null) throw new ArgumentNullException("item");

            // If the item's parent is not us, then the
            // item cannot be in our childern collection
            if (item.parent != this)
            {
                Debug.Assert(childern == null || !childern.Contains(item));
                return;
            }

            item.AssignParent(null);

            Debug.Assert(childern != null);
            childern.Remove(item);
        }

        void AssignParent(SceneItem item)
        {
            parent = item;
            InvalidateWorld();
        }

        // To prevent infinite recursion when traversing the SceneItem tree
        // a SceneItem cannot be a child or descendant of itself
        static void CheckParentingCycle(SceneItem bottom, SceneItem toFind)
        {
            Debug.Assert(bottom != null);
            Debug.Assert(toFind != null);

            SceneItem item = bottom;

            while (item != null)
            {
                if (item == toFind)
                    throw new ArgumentException("A circular reference has been made. A SceneItem cannot be a descendant of itself.");

                item = item.parent;
            }
        }

        #endregion

        #region Local and World space transformations

        WorldTransform world;
        LocalTransform local;
        bool inheritRotation = true;
        bool inheritScale = true;

        public SceneItem()
        {
            world = new WorldTransform(this);
            local = new LocalTransform(this);
        }

        /// <summary>
        /// Gets or sets a value indicating whether ancestors affect world rotation.
        /// </summary>
        /// <value><c>true</c> if ancestors should affect world rotation; otherwise, <c>false</c>.</value>
        public bool InheritRotation
        {
            get
            {
                return inheritRotation;
            }
            set
            {
                inheritRotation = value;
                InvalidateWorld();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether ancestors affect world scale.
        /// </summary>
        /// <value><c>true</c> if ancestors should affect world scale; otherwise, <c>false</c>.</value>
        public bool InheritScale
        {
            get
            {
                return inheritScale;
            }
            set
            {
                inheritScale = value;
                InvalidateWorld();
            }
        }

        public Transform3D World
        {
            get { return world; }
        }

        public Transform3D Local
        {
            get { return local; }
        }

        internal void InvalidateWorld()
        {
            world.Invalidate();
            InvalidateChildern();
        }

        internal void InvalidateChildern()
        {
            if (childern != null)
                for (int i = 0; i < childern.Count; ++i)
                    childern[i].InvalidateWorld();
        }

        #endregion

        #region Culling and Rendering

        int layer;
        RenderItem renderItem;
        bool visible = true;

        /// <summary>
        /// Gets or sets the layer.
        /// </summary>
        /// <value>The layer.</value>
        public int Layer
        {
            get { return layer; }
            set { layer = value; }
        }

        public RenderItem RenderItem
        {
            get { return renderItem; }
            set { renderItem = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="SceneItem"/> is visible.
        /// </summary>
        /// <value><c>true</c> if visible; otherwise, <c>false</c>.</value>
        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        protected internal virtual void Cull(Culler culler)
        {
            if (!visible)
                return;

            if (renderItem != null)
            {
                if (culler.IsVisible(this))
                    culler.Add(this);
            }

            if (childern != null)
            {
                for (int i = 0; i < childern.Count; i++)
                {
                    childern[i].Cull(culler);
                }
            }
        }

        #endregion

        #region Custom Data

        object tag;

        public object Tag
        {
            get { return tag; }
            set { tag = value; }
        }

        public T GetTag<T>()
        {
            return (T)tag;
        }

        #endregion
    }
}
